﻿#region Copyright(c) Anton Shelin, Vladimir Timashkov. All Rights Reserved.
// -----------------------------------------------------------------------------
// Copyright(c) 2010 Anton Shelin, Vladimir Timashkov. All Rights Reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   1. No Trademark License - Microsoft Public License (Ms-PL) does not grant you rights to use
//      authors names, logos, or trademarks.
//   2. If you distribute any portion of the software, you must retain all copyright,
//      patent, trademark, and attribution notices that are present in the software.
//   3. If you distribute any portion of the software in source code form, you may do
//      so only under this license by including a complete copy of Microsoft Public License (Ms-PL)
//      with your distribution. If you distribute any portion of the software in compiled
//      or object code form, you may only do so under a license that complies with
//      Microsoft Public License (Ms-PL).
//   4. The names of the authors may not be used to endorse or promote products
//      derived from this software without specific prior written permission.
//
// The software is licensed "as-is." You bear the risk of using it. The authors
// give no express warranties, guarantees or conditions. You may have additional consumer
// rights under your local laws which this license cannot change. To the extent permitted
// under your local laws, the authors exclude the implied warranties of merchantability,
// fitness for a particular purpose and non-infringement.
// -----------------------------------------------------------------------------
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using SharpDom.Attributes;

namespace SharpDom
{
    public enum EmptinessType
    {
        /// <summary>Empty tgas can't have any child tags</summary>
        /// <example>area, base, basefont, br, col, frame, hr, img input, isindex, link, meta, param</example>
        EmptyTag,
        /// <summary>All other tags besides empty tags</summary>
        StandardTag
    }

    /// <summary>Represents a leaf tag in the tag tree.
    /// In other words, the tag can't contain nested tags.</summary>
    /// <example>BR tag, literal tag, etc</example>
    public abstract class TreeLeafTag
    {
        /// <summary>Creates new tag out of hierarchy</summary>
        /// <param name="name">Name of the tag</param>
        /// <param name="emptinessType">Type of emptyness of the tag</param>
        protected TreeLeafTag(string name, EmptinessType emptinessType = EmptinessType.EmptyTag)
        {
            TagName = name;
            EmptinessType = emptinessType;
            Attributes = new Dictionary<string, TagAttribute>();
        }

        /// <summary>Name of the tag, ex. "span"</summary>
        public virtual string TagName { get; protected set; }
        /// <summary>Type of the emptyness - affects the type of rendering</summary>
        public virtual EmptinessType EmptinessType { get; set; }

        #region Hierarchy

        /// <summary>Reference to parent tag in tag hierarchy</summary>
        protected TreeNodeTag _parent;

        /// <summary>Parent of this tag in tag hierarchy</summary>
        public virtual TreeNodeTag Parent
        {
            get { return _parent; }
            set
            {
                if (_parent != null && _parent.Children.Contains(this))
                {
                    _parent.Children.Remove(this);
                }
                if (value != null)
                {
                    value.Children.Add(this);
                }
                _parent = value;
            }
        }

        /// <summary>Parent of this tag in tag hierarchy</summary>
        public virtual TreeNodeTag RealParent
        {
            get
            {
                if (Parent == null) return null;
                if ((Parent as ContainerTag) == null) return Parent;
                return Parent.RealParent;
            }
        }

        #endregion

        #region Attributes

        /// <summary>Attributes of the tag</summary>
        internal virtual Dictionary<string, TagAttribute> Attributes { get; private set; }

        /// <summary>Helper method to get tag's attribute by given attribute type</summary>
        /// <typeparam name="TAttribute">Type of tag's attribute</typeparam>
        /// <returns>Tag's attribute</returns>
        internal virtual TAttribute GetAttr<TAttribute>() where TAttribute : TagAttribute
        {
            if (!Attributes.ContainsKey(typeof(TAttribute).Name)) return null;
            return (TAttribute)Attributes[typeof(TAttribute).Name];
        }

        /// <summary>Attach attribute to the tag</summary>
        /// <typeparam name="TAttribute">Type of the attribuet to attach</typeparam>
        /// <param name="attribute">Attribute to attach</param>
        internal virtual void SetAttr<TAttribute>(TAttribute attribute) where TAttribute : TagAttribute
        {
            if (GetAttr<TAttribute>() != null) Attributes.Remove(typeof(TAttribute).Name);
            if (attribute != null) Attributes[typeof(TAttribute).Name] = attribute;
        }

        #endregion

        #region Render

        /// <summary>Determines accumulated indent size of the current tag on rendering stage</summary>
        public virtual int IndentSize
        {
            get { return (RealParent == null ? 0 : RealParent.IndentSize + 4); }
        }

        /// <summary>Renders indent in front of the tag being rendered</summary>
        /// <param name="writer">Writer</param>
        /// <param name="includeIndents">Specify whether to include indents</param>
        public virtual void RenderBeforeTag(TextWriter writer, bool includeIndents)
        {
            if (!includeIndents) return;
            writer.Write("{0," + IndentSize + "}", string.Empty);
        }

        /// <summary>Takes part in HTML generation</summary>
        /// <param name="writer">Destionation of rendering</param>
        /// <param name="includeIndents">Specify whether to include indents</param>
        public virtual void Render(TextWriter writer, bool includeIndents)
        {
            FireOnPreRender();
            RenderBeforeTag(writer, includeIndents);
            writer.Write("<{0}", TagName);
            foreach (var attribute in Attributes)
            {
                writer.Write(" ");
                attribute.Value.Render(writer);
            }
            RenderClosingTag(writer, includeIndents);
            RenderAfterTag(writer, includeIndents);
            FireOnPostRender();
        }

        /// <summary>Renders closing part of the tag</summary>
        /// <param name="writer">Destination of rendering</param>
        /// <param name="includeIndents">Specify whether to include indents</param>
        public virtual void RenderClosingTag(TextWriter writer, bool includeIndents)
        {
            if (EmptinessType == EmptinessType.EmptyTag) writer.Write(" />");
            else writer.Write("></{0}>", TagName);
        }

        /// <summary>Renders new line after of the tag being rendered</summary>
        /// <param name="writer">Writer</param>
        /// <param name="includeIndents">Specify whether to include indents</param>
        public virtual void RenderAfterTag(TextWriter writer, bool includeIndents)
        {
            if (!includeIndents) return;
            writer.WriteLine();
        }

        #endregion

        #region Maintenability

        /// <summary>Any purpose label attached on any stage to the tag</summary>
        public virtual object TagLabel { get; set; }

        /// <summary>Internal ID of the tag - contains IDs of all parents.
        /// Each section is just a order number of tag in parent's collection.</summary>
        public virtual int[] InternalId
        {
            get
            {
                var id = new List<int>();
                if (Parent != null)
                {
                    id.AddRange(Parent.InternalId);
                    id.Add(Parent.Children.IndexOf(this));
                }
                return id.ToArray();
            }
        }

        /// <summary>Delegate of event happened against the tag</summary>
        /// <param name="sourceTag">Tag firing the event</param>
        /// <param name="reasonTag">Tag on which event initially happens</param>
        public delegate void TagEventDelegate(TreeLeafTag sourceTag, TreeLeafTag reasonTag);

        /// <summary>Event when the tag is being checked for correct adoptability</summary>
        public event TagEventDelegate OnCheckAdoptability;
        protected virtual void FireOnCheckAdoptability(TreeLeafTag tag = null)
        {
            if (OnPreRender != null) OnCheckAdoptability(this, tag ?? this);
            if (Parent != null) Parent.FireOnCheckAdoptability(tag ?? this);
        }

        /// <summary>Event when the tag is being rendered</summary>
        public event TagEventDelegate OnPreRender;
        protected virtual void FireOnPreRender(TreeLeafTag tag = null)
        {
            if (OnPreRender != null) OnPreRender(this, tag ?? this);
            if (Parent != null) Parent.FireOnPreRender(tag ?? this);
        }

        /// <summary>Event when the tag is rendered</summary>
        public event TagEventDelegate OnPostRender;
        protected virtual void FireOnPostRender(TreeLeafTag tag = null)
        {
            if (OnPostRender != null) OnPostRender(this, tag ?? this);
            if (Parent != null) Parent.FireOnPostRender(tag ?? this);
        }

        #endregion

        #region Conversions

        /// <summary>Converts .NET Boolean object into literal tag</summary>
        public static implicit operator TreeLeafTag(bool value) { return new LiteralTag { Text = value.ToString() }; }

        /// <summary>Converts .NET Int8 object into literal tag</summary>
        public static implicit operator TreeLeafTag(byte value) { return new LiteralTag { Text = value.ToString() }; }

        /// <summary>Converts .NET Int16 object into literal tag</summary>
        public static implicit operator TreeLeafTag(short value) { return new LiteralTag { Text = value.ToString() }; }

        /// <summary>Converts .NET Uint16 object into literal tag</summary>
        public static implicit operator TreeLeafTag(ushort value) { return new LiteralTag { Text = value.ToString() }; }

        /// <summary>Converts .NET Int32 object into literal tag</summary>
        public static implicit operator TreeLeafTag(int value) { return new LiteralTag { Text = value.ToString() }; }

        /// <summary>Converts .NET Uint32 object into literal tag</summary>
        public static implicit operator TreeLeafTag(uint value) { return new LiteralTag { Text = value.ToString() }; }

        /// <summary>Converts .NET Int64 object into literal tag</summary>
        public static implicit operator TreeLeafTag(long value) { return new LiteralTag { Text = value.ToString() }; }

        /// <summary>Converts .NET Uint64 object into literal tag</summary>
        public static implicit operator TreeLeafTag(ulong value) { return new LiteralTag { Text = value.ToString() }; }

        /// <summary>Converts .NET DateTime object into literal tag</summary>
        public static implicit operator TreeLeafTag(DateTime value) { return new LiteralTag { Text = value.ToString() }; }

        /// <summary>Converts .NET Guid object into literal tag</summary>
        public static implicit operator TreeLeafTag(Guid value) { return new LiteralTag { Text = value.ToString() }; }

        /// <summary>Converts .NET Char object into literal tag</summary>
        public static implicit operator TreeLeafTag(char value) { return new LiteralTag { Text = value.ToString() }; }

        /// <summary>Converts .NET String object into literal tag</summary>
        public static implicit operator TreeLeafTag(string value) { return new LiteralTag { Text = value }; }

        /// <summary>Converts .NET Uri object into literal tag</summary>
        public static implicit operator TreeLeafTag(Uri value) { return new LiteralTag { Text = value.ToString() }; }

        #endregion
    }

    /// <summary>Represents a node tag in the tag tree.
    /// In other words, the tag can contain nested tags.</summary>
    /// <example>HTML tag, DIV tag, etc</example>
    public abstract class TreeNodeTag : TreeLeafTag
    {
        /// <summary>Default constructor, creates empty Children list and calls base constructor</summary>
        /// <param name="name">Name of the tag</param>
        /// <param name="emptinessType">Type of emptyness of the tag</param>
        protected TreeNodeTag(string name, EmptinessType emptinessType) : base(name, emptinessType)
        {
            Children = new TagList(this);
        }

        /// <summary>Children tags in tag hierarchy</summary>
        public virtual TagList Children { get; protected set; }

        #region Render

        /// <summary>Renders closing part of the tag</summary>
        /// <param name="writer">Destination of rendering</param>
        /// <param name="includeIndents">Specify whether to include indents</param>
        public override void RenderClosingTag(TextWriter writer, bool includeIndents)
        {
            if (Children.Count == 0)
            {
                base.RenderClosingTag(writer, includeIndents);
            }
            else
            {
                writer.Write(">");
                RenderAfterTag(writer, includeIndents);
                Children.Render(writer, includeIndents);
                RenderBeforeTag(writer, includeIndents);
                writer.Write("</{0}>", TagName);
            }
        }

        #endregion
    }

    /// <summary>Base class for all tags with possible nested tags.
    /// This class addes indexer support to its parent superclass.</summary>
    public abstract class IndexedTag : TreeNodeTag
    {
        /// <summary>Calls base class supporting tag name</summary>
        /// <param name="name">Name of the tag</param>
        /// <param name="emptinessType">Type of emptyness of the tag</param>
        protected IndexedTag(string name, EmptinessType emptinessType = EmptinessType.StandardTag) : base(name, emptinessType)
        {
        }

        /// <summary>Indexer accepting an array of nested tags</summary>
        /// <param name="items">Child tags</param>
        /// <returns>This instance</returns>
        public virtual TreeNodeTag this[params TreeLeafTag[] items]
        {
            get
            {
                foreach (var item in items)
                {
                    Children.Add(item);
                }
                return this;
            }
        }

        /// <summary>Adds custom attribute to the tag</summary>
        /// <param name="attrName">Name of the custom attribute</param>
        /// <param name="attrValue">Value of the custom attribute</param>
        /// <returns>This instance</returns>
        public virtual IndexedTag ext(string attrName, string attrValue)
        {
            if (!string.IsNullOrEmpty(attrName) && !string.IsNullOrEmpty(attrValue))
            {
                var attribute = new CustomAttribute(attrName) { AttributeValue = attrValue };
                Attributes[attrName] = attribute;
            }
            return this;
        }
    }
}
